Skip to content

Constrain the deferred declaration path to a dotted name (#24)#39

Merged
yavorpanayotov merged 1 commit into
mainfrom
deferred-path-grammar-24
Jun 12, 2026
Merged

Constrain the deferred declaration path to a dotted name (#24)#39
yavorpanayotov merged 1 commit into
mainfrom
deferred-path-grammar-24

Conversation

@yavorpanayotov

@yavorpanayotov yavorpanayotov commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Closes #24. Implements the direction agreed in the issue discussion, including option (a) for the quoted location hint: the deferred path is constrained at the parser level to a dotted name with an optional use-alias qualifier, and the quoted location hint becomes part of the grammar.

What changes

Grammar. deferred accepts Name, Name.field.sub, alias/Name, alias/Name.field, optionally followed by a quoted location hint on the same line. Calls, operators, comparisons, and parenthesised forms no longer parse as paths.

  • In declaration position there is no division to disambiguate from, so / always introduces a qualifier and a missing name after it is a hard error — same rationale as fulfils alias/MyContract (9cd9463).
  • The path stops before a dangling .: the declaration still forms (so the location-hint check keeps covering the line, warning under the TypeScript capture's name, dot included) and the leftover token errors at declaration level — identically in both front ends now that the extension surfaces WASM parse diagnostics (TypeScript analyzer discards WASM parse diagnostics #25/Surface WASM parse diagnostics in the TS analyzer (#25) #27).
  • Line guards keep a dangling . or / from absorbing the next declaration's leading keyword across lines (newlines are not tokens and keywords are word tokens — caught while testing, with regression tests on both).
  • deferred Foo.bar "detailed/foo.allium" previously emitted expected declaration ..., found string even though both analyzers' suppression predicate and the check-tool behaviour spec bless the form. It now parses, stored as location_hint on DeferredDecl.

AST. The path stays an Expr (Ident/QualifiedName/MemberAccess), per constraint 1 on the issue — collect_qualified_references and the WASM adapter consume it unchanged. DeferredDecl gains location_hint: Option<StringLiteral>; the TS mirror type follows.

Behaviour matrix (verified against both built analyzers):

line before after
deferred Foo.bar warns warns (unchanged)
deferred billing/InvoiceWorkflow warns under billing warns under billing (unchanged)
deferred Foo.bar "detailed/foo.allium" parse error + no warn parses cleanly, hint stored
deferred Foo("x"), deferred Foo = "x" silent (zero diagnostics) parse error on the leftover tokens, both sides
deferred (Foo) silent expected deferred name, found '(', both sides
deferred Foo. Rust parse error / TS warning (divergent) both warn under Foo. + one shared parse error
deferred billing/ absorbed the next line's keyword expected deferred name after '/'

Docs

  • v3 language reference: deferred section documents the accepted path forms, the qualified form, and both hint conventions.
  • rust-checker-parity.md §6: parity scope updated — malformed deferred lines now produce identical diagnostic sets on both sides.
  • allium-check-tool-behaviour.allium: new DeferredPathConstrainedToDottedName rule.

Cross-repo follow-up

juxt/tree-sitter-allium mirrors the old grammar (deferred_declaration: seq("deferred", field("path", $._expression)), grammar.js:186). Once the shape is agreed here, the sibling change is roughly:

deferred_declaration: ($) =>
  seq("deferred", field("path", $.deferred_path), optional(field("hint", $.string_literal))),
deferred_path: ($) =>
  seq($.identifier, optional(seq("/", $.identifier)), repeat(seq(".", $.identifier))),

Verification

  • cargo test --workspace: 559 tests pass (374 in the parser crate — 9 new grammar tests, 1 new analysis test).
  • npm run test against the rebuilt WASM: 330 pass (2 new wasm-adapter tests); npm run lint: 0 errors (2 pre-existing warnings on main).
  • Versioning note: per docs/versioning.md rule 1 this is a grammar change → minor bump (3.3.0) at release time; not bumped in this PR, matching how 9cd9463 landed.

🤖 Generated with Claude Code

parse_deferred_decl read the path with parse_expr(0), a full expression
parse. No consumer could use that expressiveness — qualified-reference
collection extracts only Expr::QualifiedName, the CLI test-plan builder
matches only Ident/MemberAccess/QualifiedName (anything else silently
dropped the deferred spec from the plan), and both analyzers' regex
lanes capture a flat dotted name. Meanwhile expression-shaped paths
(deferred Foo("x"), deferred Foo = "x", deferred (Foo)) parsed with
zero diagnostics, hiding spec errors.

The path is now a dotted name with an optional use-alias qualifier:
Name, Name.field.sub, alias/Name, alias/Name.field. In declaration
position there is no division to disambiguate from, so `/` always
introduces a qualifier (mirroring `fulfils alias/Name`, 9cd9463).
The path stays an Expr (Ident/QualifiedName/MemberAccess), so
qualified-reference collection and the WASM AST mirror are unchanged.

The path stops before a dangling `.` so the declaration still forms —
the location-hint check keeps covering the line (warning named after
the TypeScript capture, dot included) and the leftover token errors at
declaration level, identically in both front ends now that the
extension surfaces WASM parse diagnostics (#25). Line guards keep a
dangling `.` or `/` from absorbing the next declaration's leading
keyword, since newlines are not tokens and keywords are word tokens.

The quoted location hint becomes part of the grammar: `deferred
Foo.bar "detailed/foo.allium"` previously emitted "expected
declaration ..., found string" even though both analyzers' suppression
predicate and the check-tool behaviour spec bless the form. It now
parses, stored as location_hint on DeferredDecl, and must share the
declaration's line.

Documents the accepted forms in the v3 language reference, updates the
parity scope in rust-checker-parity.md §6, and adds the grammar rule to
the check-tool behaviour spec. The tree-sitter grammar mirror
(juxt/tree-sitter-allium deferred_declaration) needs a matching change.

Closes #24.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@yavorpanayotov yavorpanayotov marked this pull request as ready for review June 12, 2026 11:16
@yavorpanayotov yavorpanayotov merged commit 030bfa3 into main Jun 12, 2026
2 checks passed
@yavorpanayotov yavorpanayotov deleted the deferred-path-grammar-24 branch June 12, 2026 11:16
yavorpanayotov added a commit to juxt/tree-sitter-allium that referenced this pull request Jun 12, 2026
deferred_declaration read its path as $._expression, so calls,
comparisons, and parenthesised forms parsed as deferred paths, and the
qualified form billing/InvoiceWorkflow parsed as division. The
reference parser constrained the path in juxt/allium-tools#24
(juxt/allium-tools#39); this mirrors that grammar:

  deferred_path: identifier ('/' identifier)? ('.' identifier)*

with an optional trailing string_literal location hint
(`deferred Foo.bar "detailed/foo.allium"`), now part of the grammar
on both sides.

Known divergence from the reference parser: the path and hint are
required to sit on one line there, which this grammar does not
enforce (newlines are extras).

Corpus: dotted-path expectation updated to the new node shape; new
cases for the qualified path, the quoted hint, and an :error test for
an expression-shaped path.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Decide the grammar of the deferred declaration path

1 participant